﻿/*
* Copyright (c) 2010-2012 Tesla Engine Group
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

using System;
using System.IO;
using System.Xml;
using Tesla.Graphics;
using Tesla.Math;

namespace Tesla.Content.Loaders.Image {
    public class BMFontLoader : ResourceLoader<SpriteFont> {
        private XmlDocument document;

        public BMFontLoader()
            : base(".fnt") {
                document = new XmlDocument();
        }

        public override SpriteFont Load(IResource resource, LoaderParameters parameters) {
            document.RemoveAll();
            document.Load(resource.OpenStream());

            XmlNode fontNode = GetFontNode();

            XmlNode charsNode;
            XmlNode kerningsNode;
            String texturePath;
            int lineHeight;

            GetFontData(fontNode, out texturePath, out lineHeight, out charsNode, out kerningsNode);

            Texture2D texture = ContentManager.LoadRelativeTo<Texture2D>(texturePath, resource);
            Glyph? defaultChar = GetDefaultCharacter(texture, charsNode);

            SpriteFont spriteFont;
            if(defaultChar.HasValue) {
                spriteFont = new SpriteFont(texture, lineHeight, defaultChar.Value);
            } else {
                spriteFont = new SpriteFont(texture, lineHeight);
            }

            GetCharacters(spriteFont, charsNode);
            if(kerningsNode != null) {
                GetKernings(spriteFont, kerningsNode);
            }

            return spriteFont;
        }

        private XmlNode GetFontNode() {
            if(!document.HasChildNodes) {
                throw new InvalidDataException("File does not contain font data.");
            }

            for(int i = 0; i < document.ChildNodes.Count; i++) {
                XmlNode node = document.ChildNodes[i];
                if(node.Name.Equals("font")) {
                    return node;
                }
            }

            throw new InvalidDataException("File does not contain font data.");
        }

        private void GetFontData(XmlNode fontNode, out String texturePath, out int lineHeight, out XmlNode charsNode, out XmlNode kerningsNode) {
            if(!fontNode.HasChildNodes) {
                throw new InvalidDataException("File does not contain font data.");
            }

            XmlNode commonNode = null;
            XmlNode pagesNode = null;
            charsNode = null;
            kerningsNode = null;
            for(int i = 0; i < fontNode.ChildNodes.Count; i++) {
                XmlNode child = fontNode.ChildNodes[i];
                switch(child.Name) {
                    case "common":
                        commonNode = child;
                        break;
                    case "pages":
                        pagesNode = child;
                        break;
                    case "chars":
                        charsNode = child;
                        break;
                    case "kernings":
                        kerningsNode = child;
                        break;
                }
            }

            if(commonNode == null || pagesNode == null || charsNode == null) {
                throw new InvalidDataException("File does not contain font data.");
            }

            if(commonNode.Attributes == null || commonNode.Attributes.Count == 0) {
                throw new InvalidDataException("Missing line height from font data.");
            }

            XmlNode heightAtt = commonNode.Attributes.GetNamedItem("lineHeight");

            if(heightAtt == null || String.IsNullOrEmpty(heightAtt.Value)) {
                throw new InvalidDataException("Missing line height from font data.");
            }

            if(!int.TryParse(heightAtt.Value, out lineHeight)) {
                lineHeight = 0;
            }

            if(pagesNode.ChildNodes.Count > 1) {
                throw new InvalidDataException("Only one page texture is supported.");
            } 

            XmlNode pageNode = pagesNode.FirstChild;

            if(pageNode == null || pageNode.Attributes == null) {
                throw new InvalidDataException("Missing font texture data.");
            }

            XmlNode textAtt = pageNode.Attributes.GetNamedItem("file");

            if(textAtt == null || String.IsNullOrEmpty(textAtt.Value)) {
                throw new InvalidDataException("Missing texture file path.");
            }

            texturePath = textAtt.Value;
        }

        private Glyph? GetDefaultCharacter(Texture2D texture, XmlNode charsNode) {
            if(!charsNode.HasChildNodes) {
                throw new InvalidDataException("Missing character data from font file.");
            }

            XmlNode child = charsNode.FirstChild;
            if(child != null) {
                Glyph def;
                CreateGlyph(child, texture, out def);
                if(def.Literal == (char) 0) {
                    return def;
                }
            }
            return null;
        }

        private void GetCharacters(SpriteFont font, XmlNode charsNode) {
            int start = 0;
            if(font.DefaultCharacter.HasValue) {
                start = 1;
            }

            Texture2D texture = font.Texture;

            for(int i = start; i < charsNode.ChildNodes.Count; i++) {
                Glyph glyph;
                CreateGlyph(charsNode.ChildNodes[i], texture, out glyph);
                font.AddGlyph(glyph);
            }
        }

        private void CreateGlyph(XmlNode charNode, Texture2D texture, out Glyph glyph) {
            XmlAttributeCollection attributes = charNode.Attributes;
            if(attributes == null || attributes.Count < 10) {
                throw new InvalidDataException("Missing character information.");
            }
            XmlNode idNode = attributes.GetNamedItem("id");
            XmlNode xNode = attributes.GetNamedItem("x");
            XmlNode yNode = attributes.GetNamedItem("y");
            XmlNode widthNode = attributes.GetNamedItem("width");
            XmlNode heightNode = attributes.GetNamedItem("height");
            XmlNode xOffsetNode = attributes.GetNamedItem("xoffset");
            XmlNode yOffsetNode = attributes.GetNamedItem("yoffset");
            XmlNode xAdvanceNode = attributes.GetNamedItem("xadvance");

            if(idNode == null || xNode == null || yNode == null || widthNode == null
                || heightNode == null || xOffsetNode == null || yOffsetNode == null || xAdvanceNode == null) {
                    throw new InvalidDataException("Missing character information.");
            }

            int scaleWidth = texture.Width;
            int scaleHeight = texture.Height;

            int id = int.Parse(idNode.Value);
            //Default (invalid char) found, put it in the proper range.
            if(id == -1) {
                id = 0;
            }
            int x = int.Parse(xNode.Value);
            int y = int.Parse(yNode.Value);
            int width = int.Parse(widthNode.Value);
            int height = int.Parse(heightNode.Value);
            int xOffset = int.Parse(xOffsetNode.Value);
            int yOffset = int.Parse(yOffsetNode.Value);
            int xAdvance = int.Parse(xAdvanceNode.Value);

            glyph = new Glyph((char) id, new Rectangle(x, y, width, height), new Vector3(xOffset, yOffset, xAdvance));
        }

        private void GetKernings(SpriteFont font, XmlNode kerningsNode) {
            if(!kerningsNode.HasChildNodes) {
                throw new InvalidDataException("Missing kerning data.");
            }

            for(int i = 0; i < kerningsNode.ChildNodes.Count; i++) {
                XmlNode child = kerningsNode.ChildNodes[i];
                XmlAttributeCollection atts = child.Attributes;
                if(atts == null) {
                    throw new InvalidDataException("Missing kerning data.");
                }
                XmlNode first = atts.GetNamedItem("first");
                XmlNode second = atts.GetNamedItem("second");
                XmlNode amount = atts.GetNamedItem("amount");

                font.AddKerning((char)int.Parse(second.Value), (char)int.Parse(first.Value), int.Parse(amount.Value));
            }
        }
    }
}
